package com.zhj.wapper;

import com.zhj.exception.DynamicCompileException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.FileReader;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;


public class DynamicJsCompileWrapper implements DynamicCompileWrapper<ScriptEngine> {

    public static final ScriptEngineManager manager = new ScriptEngineManager();

    private final ScriptEngine engine;

    public static final String engineName = "js";

    /**
     * 设置锁，防止使用脚本时出现并发参数绑定问题
     */
    private final ReentrantLock lock;

    public DynamicJsCompileWrapper() {
        //指定默认引擎名称
        this.engine = manager.getEngineByName(engineName);
        lock = new ReentrantLock();
    }

    /**
     * 根据脚本执行
     *
     * @param eval   脚本
     * @param params
     * @return
     * @throws DynamicCompileException
     */
    @Override
    public Object execFunction(String eval, Map<String, Object> params) throws DynamicCompileException {
        Objects.requireNonNull(eval, "脚本不能为空");
        Object result = null;
        lock.lock();
        try {
            bindParams(params, engine);
            result = engine.eval(eval);
        } catch (Exception e) {
            throw DynamicCompileException.newInstance(e.getMessage());
        } finally {
            lock.unlock();
        }
        return result;
    }

    @Override
    public Object execFunctionForMethodName(String eval, String methodName, Map<String, Object> params) throws DynamicCompileException {
        Objects.requireNonNull(methodName, "方法名称不能为空");
        Object result = null;
        try {
            lock.lock();
            bindParams(params, engine);
            if (StringUtils.isNotBlank(methodName)) {
                engine.eval(eval);
                if (engine instanceof Invocable) {
                    Invocable invocable = (Invocable) engine;
                    result = invocable.invokeFunction(methodName);
                }
            } else {
                result = engine.eval(eval);
            }
        } catch (Exception e) {
            throw DynamicCompileException.newInstance(e.getMessage());
        } finally {
            lock.unlock();
        }
        return result;
    }


    @Override
    public Object execFunctionForFile(String path, String methodName, Map<String, Object> params) throws DynamicCompileException {
        File file = new File(path);
        if (file.exists()) {
            throw new NullPointerException("文件脚本不存在");
        }
        String fileName = file.getName();
        String prefix = StringUtils.substringAfterLast(fileName, ".");
        if (!engineName.equals(prefix)) {
            throw new IllegalArgumentException(String.format("当前脚本引擎：%s，不能执行当前脚本代码：%s", engineName, prefix));
        }
        Object result = null;
        try {
            lock.lock();
            bindParams(params, engine);
            FileReader reader = new FileReader(file);
            if (StringUtils.isNotBlank(methodName)) {
                engine.eval(reader);
                if (engine instanceof Invocable) {
                    Invocable invocable = (Invocable) engine;
                    result = invocable.invokeFunction(methodName);
                }
            } else {
                result = engine.eval(reader);
            }
        } catch (Exception e) {
            throw DynamicCompileException.newInstance(e.getMessage());
        } finally {
            lock.unlock();
        }
        return result;
    }

    @Override
    public ScriptEngine getScriptEngine(String scriptName) {
        return this.engine;
    }

    /**
     * 创建参数绑定
     *
     * @param params
     * @param scriptEngine
     */
    private void bindParams(Map<String, Object> params, ScriptEngine scriptEngine) {
        if (!CollectionUtils.isEmpty(params)) {
            Bindings bindings = scriptEngine.createBindings();
            bindings.putAll(params);
            scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
        }
    }

    @Override
    public Lock getLock() {
        return this.lock;
    }

    /**
     * 创建实例
     * @return
     */
    public static DynamicCompileWrapper<ScriptEngine> createInstance() {
        return new DynamicJsCompileWrapper();
    }
}
